
/*
   READ FIRST: This is just a hard copy of the client.c from project 1 sample
   But I also include some changes, the one I make change will have //MAKE CHANGE after the code
   */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>      // define structures like hostent
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "packet.h"

void error(char *msg)
{
   perror(msg);
   exit(0);
}

// calculate pl and pc
// return -1 if loss
// return -2 if coruption
// return 0 if normal
int calculateP(double pl, double pc); 

// function for recreating the file
int recreateFile(char* filename, packet* arrayOfPacket, int num_packets);

int main(int argc, char *argv[])
{
   int sockfd; //Socket descriptor
   int portno;
   struct sockaddr_in serv_addr;
   //contains tons of information, including the server's IP address
   struct hostent *server;

   // VARIABLE DECLARATION FOR PROJECT 2
   double pl, pc; // probability
   char filename[256];
   FILE* fd;
   int num_packets; // total number of packets
   int max; //gurading variable

   srand(time(0));
   /* Checking for valid argument input*/
   if (argc != 4 && argc != 6) // number of argument too short
   {
      fprintf(stderr,"Usage: ./receiver <sender_hostname> <sender_portnumber> <filename> Pl Pc\n OR ./receiver <sender_hostname> <sender_portnum> <filename>\n");
      exit(0);
   }
   else if (argc == 4) // sending without loss or corruption
   {
      portno = atoi(argv[2]);
      strcpy(filename, argv[3]);
      pl = 0;
      pc = 0;
   }
   else if (argc == 6) // 6 argument, the 2nd way to call sender
   {
      portno = atoi(argv[2]);
      strcpy(filename, argv[3]);
      pl = strtod(argv[4],0);
      pc = strtod(argv[5],0);
   }
   /* End of checking for arguments input*/

   //create a new socket
   sockfd = socket(AF_INET, SOCK_DGRAM, 0); 
   if (sockfd < 0)
   { 
      perror("ERROR opening socket");
      exit(1);
   }
   // get host's name
   server = gethostbyname(argv[1]); 
   if (server == NULL) {
      fprintf(stderr,"ERROR, no such host\n");
      exit(0);
   }

   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET; //initialize server's address
   bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
   serv_addr.sin_port = htons(portno);

   if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) //establish a connection to the server
      error("ERROR connecting");


   /* MAIN LOGIC SHOULD BE HERE */
   // create a packet with filename and send to sender
   packet new_pck;
   socklen_t addr_len = sizeof(struct sockaddr);
   int send_flag, rcv_flag;
   new_pck.seqnum = -99; // indicate it's a file name
   strcpy(new_pck.payload, filename);

   // seding file request
   printf("Sending request for: %s\n", filename);
   send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
         (struct sockaddr*)&(serv_addr), addr_len);
   if (send_flag == -1)
   {
      perror("sendto");
      exit(1);
   }
   printf("Sent request for %s\n", filename);
   // receiving respond from the server
   rcv_flag = recvfrom(sockfd,&new_pck,sizeof(packet),0,
         (struct sockaddr*)&serv_addr,
         &addr_len);
   if (rcv_flag == -1)
   {
      perror("ERROR: recvfrom");
      exit(3);
   }

   // if it's a "DNE" mean does not exist, end the program
   // it it's exist, send an REQ_ACK
   if (!strcmp(new_pck.payload, "DNE"))
   {
      printf("Received: file does not exist\n");
      return 0;
   }
   else
   {
      // get the total number of packet
      max = num_packets = new_pck.seqnum;
      printf("Number of packets: %d\n", num_packets);

      printf("File exist\n");
      strcpy(new_pck.payload,"REQ_ACK");

      send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
            (struct sockaddr*)&(serv_addr), addr_len);
      if (send_flag == -1)
      {
         perror("sendto");
         exit(1);
      }

      printf("Sent: REQ_ACK\n");
      printf("===================\n");
      /* RECEIVING FILE SECTION */

      // Variables declaration
      int expectedseqnum = 0;
      int pck_count = 0;
      int pck_received = 0;
      int pck_lost = 0;
      int pck_outoforder = 0;
      int pck_corrupt = 0;
      char filePath[256] = "received_files/";
      strcat(filePath, filename);

      // open file for writing
      fd = fopen(filePath,"w+"); // open file for overwriting
      if (fd == 0)
      {
         perror("Error open file for writing \n");
         exit(1);
      }

      while (num_packets > 0)
      {
         rcv_flag = recvfrom(sockfd,&new_pck,sizeof(packet),0,
               (struct sockaddr*)&serv_addr,
               &addr_len);
         //printf("Receiving packet with seqnum = %d\n", new_pck.seqnum);
         if (rcv_flag == -1)
         {
            perror("ERROR: recvfrom receiving file section");
            exit(3);
         }

         /* LOSS PACKET */
         // ignore if lost
         if (new_pck.seqnum == -1) // indicate packet loss
         {
            pck_count++;
            pck_lost++;
            continue;
         }

         // Calculate the checksum to make sure the packet do not corrupted
         int checksum = calculateChecksum(&new_pck);

         // corruption occur
         // ignore it
         if(checksum != new_pck.checksum) 
         {
	   printf("+++++++++++++++++++++\n");
            printf("Packet number %d CORRUPTED\n", new_pck.seqnum);
	    printf("Header's checksum: %d\n", new_pck.checksum);
	    printf("Calculated checksum: %d\n", checksum);
	    printf("++++++++++++++++++++++\n");
            pck_corrupt++;
            continue;
         }
         else // nothing
         {
            printf("Packet number %d NOT CORRUPTED\n", new_pck.seqnum);
         }

	 // calculate probability for ack loss, corruption
	 int prob = calculateP(pl, pc);

	 printf("==========================\n");
	 printf("Expected seqnum = %d\n", expectedseqnum); 
	 printf("Received: packet number: %d\n", new_pck.seqnum);
	 printf("Packet checksum %d\n", new_pck.checksum);
	 printf("Packet ACK num: %d \n", new_pck.acknum);
         // if received correctly, write to file
         // received correctly
         if (new_pck.seqnum == expectedseqnum)
         {
	   /*
	   printf("==========================\n");
	   printf("Expected seqnum = %d\n", expectedseqnum); 
            printf("Received: packet number: %d\n", new_pck.seqnum);
            printf("Packet checksum %d\n", new_pck.checksum);
            printf("Packet ACK num: %d \n", new_pck.acknum);
	   */
            int flag;		
            flag = fwrite(&(new_pck.payload), 1, strlen(new_pck.payload), fd);
            if(flag == -1)
            {
               perror("ERROR: fwrite");
               exit(1);
            }
	    expectedseqnum++;
	    num_packets--;
            // send cummulative ACK (GO BACK END)
            if (num_packets >= 0)
            {	               	      
	      if (prob == 0)
		{
		  send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
				     (struct sockaddr*)&(serv_addr), addr_len);
		  if (send_flag == -1)
		    {
		      perror("sendto");
		      exit(1);
		    }
		  printf("Sent: ACK[%d]\n", new_pck.acknum);
		  printf("===========================\n");

		  //expectedseqnum++;
		  //num_packets--;
		  pck_received++;
		  pck_count++;
		}
	      else if (prob == -1) // ACK loss
		{
		  // doing nothing
		  printf("ACK[%d] will be LOST\n", new_pck.acknum);
		}
	      else if (prob == -2) // ACK corruption
		{
		  new_pck.acknum = CORRUPTED_ACK;
		  send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
				     (struct sockaddr*)&(serv_addr), addr_len);
		  if (send_flag == -1)
		    {
		      perror("sendto");
		      exit(1);
		    }

		  printf("Sent: CORRUPTED ACK[%d]\n", new_pck.acknum);
		  printf("===========================\n");
		}
            }
            else
            {
               printf("Received all packets \n");
               //return 0;
            }
         } // end if
         else // incorrect packet
         {
	    printf("Received: Incorrect packet\n");
	    new_pck.acknum = expectedseqnum-1;
	    if(prob == 0)
	      {	
		send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
				   (struct sockaddr*)&(serv_addr), addr_len);
		if (send_flag == -1)
		  {
		    perror("sendto");
		    exit(1);
		  }
		printf("Sent: ACK[%d]\n", new_pck.acknum);
		printf("===========================\n");
		
		pck_count++;
		pck_outoforder++;
	      }
	    else if (prob == -2) // ACK loss
	      {
		// doing nothin
	      }
	    else if (prob == -1)
	      {
		new_pck.acknum = CORRUPTED_ACK;
		send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
				   (struct sockaddr*)&(serv_addr), addr_len);
		if (send_flag == -1)
		  {
		    perror("sendto");
		    exit(1);
		  }
		printf("Sent: CORRUPTED ACK[%d]\n", new_pck.acknum);
		printf("===========================\n");
	      }
         } // end else

      } // end while
      /* END RECEIVING FILE SECTION */

      printf("File received\n");

      /*
      // let the sender know that the file has succesfully received
      new_pck.acknum = max;
      send_flag = sendto(sockfd, &new_pck, sizeof(packet), 0,
				   (struct sockaddr*)&(serv_addr), addr_len);
      if (send_flag == -1)
	{
	  perror("sendto");
	  exit(1);
	}
      	printf("Sent: Finished\n");
	printf("===========================\n");
      */
   } // end else

   /* close(sockfd); //close socket */

   return 0;
}

int recreateFile(char* filename, packet* arrayOfPacket, int num_packets)
{
   FILE* filefd;
   int flag; // flag for checking if writing succesfully
   int i;

   filefd = fopen(filename,"w+"); // open file for overwriting
   if (filefd == 0)
   {
      perror("Error open file for writing \n");
      exit(1);
   }

   for(i = 0; i<num_packets; i++)
   {
      flag = fwrite(arrayOfPacket[i].payload, sizeof(char), PACKET_SIZE - 1, filefd);
   }

   fclose(filefd);
   return 0;
}

// calculate pl and pc
// return -1 if loss
// return -2 if coruption
// return 0 if normal
int calculateP(double pl, double pc)
{
   if ((double)(rand()%100) < (pl*100)) // loss       
   {
     //printf("ACK loss\n");
      return -1;
   }
   else if ((double)(rand()%100) < (pc*100)) // coruption 
   {
     //printf("ACK corruption\n");
      return -2;
   }
   else // normal
   {
      return 0;
   }

   return 0;
}
